package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.BooleanQuery;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;


@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private GoodsRepository repository;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    public Goods buildGoods(SpuDTO spu) {
        //封装all
        String all = spu.getName() + spu.getBrandName() + spu.getCategoryName();

        //封装skus  id  价钱  图片
        List<SkuDTO> skuDTOList = itemClient.querySkuBySpuId(spu.getId());
        //new一个新的集合，只存放四个属性的sku
        List<Map<String, Object>> skuList = new ArrayList<>();
        skuDTOList.forEach(skuDTO -> {
            //把每一个sku只留下四个属性
            Map<String, Object> map = new HashMap<>();
            map.put("id", skuDTO.getId());
            map.put("title", skuDTO.getTitle());
            map.put("price", skuDTO.getPrice());
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            skuList.add(map);
        });

        //封装所有sku的价格
        Set<Long> priceSet = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //封装规格参数
        Map<String, Object> specs = new HashMap<>();
        //得到规格参数map中的key
        List<SpecParamDTO> specParamDTOList = itemClient.querySpecGroupByCategoryId(null, spu.getCid3(), true);
        //得到规格参数map中的value
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetailById(spu.getId());
        //得到通用规格参数的值字符串,并将通用规格参数的值字符串转成map
        String genericSpec = spuDetailDTO.getGenericSpec();
        Map<Long, String> genericSpecMap = JsonUtils.toMap(genericSpec, Long.class, String.class);
        //得到特有规格参数的值字符串,并将特有规格参数的值字符串转成map
        String specialSpec = spuDetailDTO.getSpecialSpec();
        Map<Long, List<String>> specialSpecMap = JsonUtils.nativeRead(specialSpec, new TypeReference<Map<Long, List<String>>>() {
        });

        //遍历规格参数，得到最终需要的key和value
        specParamDTOList.forEach(param -> {
            String key = param.getName();
            Object value = null;
            //如果是通用规格参数从genericSpecMap中取value值
            if (param.getGeneric()) {
                value = genericSpecMap.get(param.getId());
            } else {
                value = specialSpecMap.get(param.getId());
            }
            //替换索引库中所有数字类型的字段值为区间。------------------------------------------------------
            if (param.getNumeric()) {
                value = chooseSegment(value, param);
            }
            specs.put(key, value);
        });


        //创建出Goods对象
        Goods goods = new Goods();
        goods.setId(spu.getId()); //sup属性
        goods.setSubTitle(spu.getSubTitle()); //sup属性
        goods.setBrandId(spu.getBrandId());  //sup属性
        goods.setCategoryId(spu.getCid3()); //sup属性
        goods.setCreateTime(spu.getCreateTime().getTime()); //sup属性

        goods.setAll(all);  //包含标题名称，分类名称三级都要，甚至品牌名称
        goods.setSkus(JsonUtils.toString(skuList)); //querySkuBySpuId(supId)方法返回
        goods.setPrice(priceSet);   //skus里面
        goods.setSpecs(specs);  //querySpecGroupByCategoryId方法但是需要自己封装

        return goods;
    }

    //处理
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    //去Elasticsearch查询
    public PageResult<GoodsDTO> search(SearchRequest searchRequest) {
        try {
            // 0.健壮性判断
            String key = searchRequest.getKey();
            if (StringUtils.isBlank(key)) {
                throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
            }
            // 1.创建原生搜索构建器
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

            // 2.组织条件
            // 2.0.source过滤，控制字段数量
            queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
            // 2.1.搜索条件
            queryBuilder.withQuery(getBaseQuery(searchRequest));
            // 2.2.分页条件
            queryBuilder.withPageable(PageRequest.of(searchRequest.getPage() - 1, searchRequest.getSize()));

            // 3.搜索结果
            AggregatedPage<Goods> goodsPageResult = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

            //得到指定页的数据
            List<Goods> goodsList = goodsPageResult.getContent();
            //转换DTO
            List<GoodsDTO> dtoList = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);

            return new PageResult<>(goodsPageResult.getTotalElements(), goodsPageResult.getTotalPages(), dtoList);
        } catch (Exception e) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }

    }

    //为查询封装条件
    private QueryBuilder getBaseQuery(SearchRequest request) {
        // 构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        // 构建基本的match查询
        //Operator.AND表示搜索内容必须包含分词后所有词条才能展示
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));

        // 构建过滤条件
        Map<String, String> filter = request.getFilter();
        if (!CollectionUtils.isEmpty(filter)) {
            for (Map.Entry<String, String> entry : filter.entrySet()) {
                // 获取过滤条件的key
                String key = entry.getKey();
                // 规格参数的key要做前缀specs.
                if (key.equals("品牌")) {
                    key = "brandId";
                } else if (key.equals("分类")) {
                    key = "categoryId";
                } else {
                    key = "specs." + key;
                }
                String value = entry.getValue();
                // 添加过滤条件
                queryBuilder.filter(QueryBuilders.termsQuery(key, value));
            }
        }
        return queryBuilder;
    }


    //查询过滤项
    public Map<String, List<?>> queryFilters(SearchRequest searchRequest) {
        try {
            //创建过滤集合
            LinkedHashMap<String, List<?>> filterMap = new LinkedHashMap<>();

            // 0.健壮性判断
            String key = searchRequest.getKey();
            if (StringUtils.isBlank(key)) {
                throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
            }
            // 1.创建原生搜索构建器
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

            // 2.组织条件
            // 2.0.source过滤，控制字段数量,这里只需要聚合结果,这个搜索结果是不要的所以条件就是空无法匹配,提高效率
            queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
            // 2.1.搜索条件
            QueryBuilder baseQuery = getBaseQuery(searchRequest);
            queryBuilder.withQuery(baseQuery);
            // 2.2.分页条件
            queryBuilder.withPageable(PageRequest.of(0, 1));
            // 显示空的source,也就是这个结果不要,但是填了上面条件查询快
            queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());


            // 3.聚合条件
            // 3.1.分类聚合
            String categoryAgg = "categoryAgg";
            queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
            // 3.2.品牌聚合
            String brandAgg = "BrandAgg";
            queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

            // 4.查询并解析结果
            AggregatedPage<Goods> goodsPageResult = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
            Aggregations aggregations = goodsPageResult.getAggregations();

            // 4.1.获取商品聚合
            LongTerms cTerms = aggregations.get(categoryAgg);
            List<CategoryDTO> categoryDTOList = handleCategoryAgg(cTerms, filterMap);
            // 4.2.获取分类聚合
            LongTerms bTerms = aggregations.get(brandAgg);
            handleBrandAgg(bTerms, filterMap);

            //判断，如果分类只有一个，继续封装规格参数
            if (categoryDTOList.size() == 1) {
                handlerParamsMap(categoryDTOList.get(0).getId(), baseQuery, filterMap);
            }
            return filterMap;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }

    }

    //封装规格参数过滤项
    private void handlerParamsMap(Long id, QueryBuilder baseQuery, LinkedHashMap<String, List<?>> filterMap) {
        //根据分类查询出所有需要过滤的规格参数
        List<SpecParamDTO> specParamDTOList = itemClient.querySpecGroupByCategoryId(null, id, true);
        // 1.创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 2.组织条件
        // 2.0.source过滤，控制字段数量,这里只需要聚合结果,这个搜索结果是不要的所以条件就是空无法匹配,提高效率
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
        // 2.1.搜索条件
        queryBuilder.withQuery(baseQuery);
        // 2.2.分页条件
        queryBuilder.withPageable(PageRequest.of(0, 1));

        //变量当前分类下所有的规格参数进行聚合条件的封装
        specParamDTOList.forEach(param -> {
            String aggName = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(aggName).field("specs." + aggName));
        });
        //聚合查询
        AggregatedPage<Goods> goodsPageResult = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //得到所有的聚合结果
        Aggregations aggregations = goodsPageResult.getAggregations();
        //循环，解析聚合结果
        specParamDTOList.forEach(param -> {
            String aggName = param.getName();
            Terms terms = aggregations.get(aggName);
            //把结果转换成string并且不是空的进行收集
            List<String> paramsValue = terms.getBuckets().stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());
            filterMap.put(aggName, paramsValue);
        });

    }

    // 4.1.获取分类聚合
    private void handleBrandAgg(LongTerms terms, LinkedHashMap<String, List<?>> filterMap) {
        List<Long> brandIds = terms.getBuckets().stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        List<BrandDTO> brandDTOList = itemClient.queryBrandByIds(brandIds);
        filterMap.put("品牌", brandDTOList);
    }

    // 4.1.获取商品聚合
    private List<CategoryDTO> handleCategoryAgg(LongTerms terms, LinkedHashMap<String, List<?>> filterMap) {
        // 解析bucket，得到id集合
        List<Long> categoryIds = terms.getBuckets().stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 根据id集合查询品牌
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(categoryIds);
        // 存入map
        filterMap.put("分类", categoryDTOList);
        return categoryDTOList;
    }

    //根据id创建页面
    public void createIndex(Long id) {
        SpuDTO spuDTO = itemClient.querySpuById(id);
        Goods goods = buildGoods(spuDTO);
        repository.save(goods);
    }

    //根据id删除
    public void deleteIndex(Long id) {
        repository.deleteById(id);
    }
}
